// Copyright 2019 Proyectos y Sistemas de Mantenimiento SL (eProsima).
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ------------------------------------------------------------------
// Modification history:
// feature: discovery support client and server
// ------------------------------------------------------------------

#ifndef XMLPARSER_REMOTELOCATORS_HPP_
#define XMLPARSER_REMOTELOCATORS_HPP_

#include <deps/common/Locator.h>
#include <deps/common/collections/ResourceLimitedVector.hpp>
#include <elog/Log.hpp>
#include <deps/common/IPLocator.h>

namespace vbsutil {
namespace xmlparser {

/**
 * Holds information about the locators of a remote entity.
 */
struct RemoteLocatorList {
    /**
     * Default constructor of RemoteLocatorList for deserialize.
     */
    RemoteLocatorList() {}

    /**
     * Construct a RemoteLocatorList.
     *
     * @param max_unicast_locators Maximum number of unicast locators to hold.
     * @param max_multicast_locators Maximum number of multicast locators to hold.
     */
    RemoteLocatorList(size_t max_unicast_locators, size_t max_multicast_locators)
        : unicast(ResourceLimitedContainerConfig::fixed_size_configuration(max_unicast_locators)),
          multicast(ResourceLimitedContainerConfig::fixed_size_configuration(max_multicast_locators)) {}

    /**
     * Copy-construct a RemoteLocatorList.
     *
     * @param other RemoteLocatorList to copy data from.
     */
    RemoteLocatorList(const RemoteLocatorList& other) : unicast(other.unicast), multicast(other.multicast) {}

    /**
     * Assign locator values from other RemoteLocatorList.
     *
     * @param other RemoteLocatorList to copy data from.
     *
     * @remarks Using the assignment operator is different from copy-constructing as in the first
     * case the configuration with the maximum number of locators is not copied. This means that,
     * for two lists with different maximum number of locators, the expression `(a = b) == b` may
     * not be true.
     */
    RemoteLocatorList& operator=(const RemoteLocatorList& other) {
        unicast = other.unicast;
        multicast = other.multicast;
        return *this;
    }

    /**
     * Adds a locator to the unicast list.
     *
     * If the locator already exists in the unicast list, or the maximum number of unicast locators
     * has been reached, the new locator is silently discarded.
     *
     * @param locator Unicast locator to be added.
     */
    void add_unicast_locator(const Locator_t& locator) {
        for (const Locator_t& loc : unicast) {
            if (loc == locator) {
                return;
            }
        }

        if (unicast.push_back(locator) == nullptr) {
            logWarning(REMOTE_LOCATOR_LIST, "Address: " << locator << " cannot add to unicast locator. ");
        } else {
            logDebug(REMOTE_LOCATOR_LIST, "Address: " << locator << " add to unicast locator. ");
        }
    }

    /**
     * Adds a locator to the multicast list.
     *
     * If the locator already exists in the multicast list, or the maximum number of multicast
     * locators has been reached, the new locator is silently discarded.
     *
     * @param locator Multicast locator to be added.
     */
    void add_multicast_locator(const Locator_t& locator) {
        for (const Locator_t& loc : multicast) {
            if (loc == locator) {
                return;
            }
        }

        // (void)multicast.push_back(locator);
        if (multicast.push_back(locator) == nullptr) {
            logWarning(REMOTE_LOCATOR_LIST,
                       "Address: " << IPLocator::ip_to_string(locator) << " cannot add to multicast locator. ");
        } else {
            logDebug(REMOTE_LOCATOR_LIST,
                     "Address: " << IPLocator::ip_to_string(locator) << " add to multicast locator. ");
        }
    }

    //! List of unicast locators
    ResourceLimitedVector<Locator_t> unicast;
    //! List of multicast locators
    ResourceLimitedVector<Locator_t> multicast;
};
/*
 * multicast max_size , multicast size , unicast max_size , unicast size ( locator[0] , locator[1] ,
 * ... )
 */
inline std::ostream& operator<<(std::ostream& output, const RemoteLocatorList& remote_locators) {
    // Stored multicast locators
    output << "{";
    if (!remote_locators.multicast.empty()) {
        output << "MULTICAST:[";
        output << remote_locators.multicast[0];
        for (auto it = remote_locators.multicast.begin() + 1; it != remote_locators.multicast.end(); ++it) {
            output << "," << *it;
        }
        output << "]";
    }

    // Stored unicast locators
    if (!remote_locators.unicast.empty()) {
        output << "UNICAST:[";
        output << remote_locators.unicast[0];
        for (auto it = remote_locators.unicast.begin() + 1; it != remote_locators.unicast.end(); ++it) {
            output << "," << *it;
        }
        output << "]";
    }
    output << "}";
    return output;
}

inline std::istream& operator>>(std::istream& input, RemoteLocatorList& locList) {
    std::istream::sentry s(input);
    locList = RemoteLocatorList();

    if (s) {
        char punct;
        char letter;
        Locator_t l;
        std::ios_base::iostate excp_mask = input.exceptions();

        try {
            input.exceptions(excp_mask | std::ios_base::failbit | std::ios_base::badbit);
            std::stringbuf sb_aux;
            Locator_t locator;

            // Read {_
            input >> punct >> letter;

            if (letter == 'M') {
                (void)input.get(sb_aux, '[');

                // Read every locator
                while (punct != ']') {
                    input >> locator;
                    locList.add_multicast_locator(locator);
                    input >> punct;
                }

                input >> letter;
            }

            if (letter == 'U') {
                input >> punct;

                // Read every locator
                while (punct != ']') {
                    input >> locator;
                    locList.add_unicast_locator(locator);
                    input >> punct;
                }

                input >> letter;
            }
        } catch (std::ios_base::failure&) {
            locList.unicast.clear();
            locList.multicast.clear();
            logWarning(REMOTE_LOCATOR_LIST, "Error deserializing RemoteLocatorList");
        }

        input.exceptions(excp_mask);
    }

    return input;
}
}  // namespace xmlparser
}  // namespace vbsutil

#endif  // INCLUDE_EDDS_RTPS_COMMON_REMOTELOCATORS_HPP_
